c++引用深入探讨
2007年06月06日 22:34:00 dead_of_winter 阅读数:884 标签: c++ iostream javascript 语言 c# 编译器 更多
引用的声明:基本格式:引用类型 &引用名=被引用对象
&运算符:声明运算符& 跟取地址运算符&和位异或运算符&没有任何关系
extern关键字:一般情况下,引用的声明必须指定被引用对象,唯一的例外是使用extern关键字
const关键字:一般情况下,被引用对象必须是有效的左值对象 但是const关键字修饰的常引用类型允许被引用对象不是左值对象
引用的使用:引用可以被当作被引用对象的别名使用,单纯的引用是无意义的,引用主要应用于四个方面:
函数参数:可修改的实参,高效的复杂对象传递方式
函数返回值:不产生复制的返回值,返回左值类型的函数
运算符重载:为++ -- << >>等运算符提供了更贴近原意的重载方式
多态:代替指针使用,实现抽象类的引用
引用的深入探讨:现在开始进入正题
1.引用占内存空间吗?
如果引用只是一个别名的话 他不应该占有内存空间 我用下面这段代码来查看
#include <iostream> using namespace std; void fa(){ int a[4]; cout<a<endl; } void fb(){ int a[4]; //int &b=a[0]; cout<a<endl; fa(); } void fc(){ int a[4]; cout<a<endl; fb(); } int main() { fc(); getchar(); return 0; } /* 0012FEE0 //fc()的a[]的地址 0012FE7C //fb()的a[]的地址 0012FE14 //fa()的a[]的地址 // 0012FEE0 0012FE7C 0012FE18 */
这段代码里用了三个嵌套的函数调用,这样fa()和fc()的栈段就把fb()夹在了中间,fb()的栈段大小变化的话,会导致fa()中a[]的地址偏移,但是并不是每个分配都会导致栈段增大的,所以应该先测试一下,确定a的合适大小,使得一旦声明变量 b的栈段就增大。
把int &b=a[0];注释掉的话 可以看到fa中输出的地址变了,fb没有变 那么引用b显然占据了内存空间 代码在g++中编译,结果也是g++的结果。
2.引用的值不可改变吗?
一个已经初始化的引用类型对象 其引用的对象不可能被合法的改变。
前面一段代码展示给我们 引用确实占据了内存空间 ,了进一步了解其本质,我们必须获得它的地址。&取地址操作符显然是无法做到的,几乎每个学c++的人都会尝试用这种办法去取引用类型的地址,但得到的都是被引用对象的地址。
还从前面的例子入手 fb中 a的地址没有改变 fa中a的地址改变 那么 int &b的分配应该在 二者之间 于是 最可能的位置就是a中的a[4] 但输出之后我发现a[4]不是,因为数组a是跟栈逆向分配的 之后我试了这样的代码
void fb(){ int a[4]; int b=20; cout<<a[-1]<<endl; fa(); }
在我的编译器中 b跟a[-1]总是相等 于是 我把int b 替换成int &b=a[0]; ok 我发现它指向了一个貌似地址的东西。改变b的指向 发现a[-1]是随之变化的 现在 我几乎可以确定 a[-1]就是b了 再用一段这样的代码来检验
void fb(){ int a[4]={1,2,3,4}; int &b=a[0]; a[-1]+=4; cout<<b<<","<<a[0]<<endl; fa(); }
引用真的是不可改变的吗? 在这个例子中 我使b指向了a[1] 而不再是a[0]
3.引用和指针
从上面的例子看出,引用的内部实现和指针并无两样。如果参考其他语言的思想的话,可以得到结论:引用就是指针常量。 在c++中,引用在语法上与指针有着明显的差异 但是,他们并没有本质不同,引用是c++中实现的一种限制比较严格的常量指针,它在参与任何运算之前自动解引用。
在使用中,推荐尽量用引用代替指针,因为引用是一种比指针更安全的类型,并且有更清晰的语义(当然指针也有适合的语义)
4.其他语言
顺便一提,在C家的其他语言中,引用几乎被作为访问对象的唯一手段。
C++:有指针,所有运算符会解引用,所有对象不作为引用,传递参数和返回值时如果不希望复制,则必须将形参指定为引用类型
C#: unsafe模式有指针,除了=之外的操作符会解引用,所有对象作为引用,传递参数和返回值时如果希望复制,则必须显式clone
Java:无指针,除了=之外的操作符会解引用,类似c#
javascript:无指针,但是可以变通,除了=之外的操作符会解引用,没有提供复制传递的方法(郁闷)
const pointer可以初始化为NULL,而&不行,即引用总是指向某个对象。
1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3) 不存在指向空值的引用,但是存在指向空值的指针。
1。 非空的差别任何情况下都不能使用指向空值的引用。一个引用必须总是指向某
2。 合法性区别在使用引用之前不需要测试他的合法性。指针必须测试。
3。 可修改区别 指针可以被重新赋值给另一个不同的对象。但是引用总是指向在初始化的时候被制定的对象,以后不能改变。但是指定的对象其内容可以改变。
应该使用指针的情况: 可能存在不指向任何对象的可能性 需要在不同的时刻指向不同的对象(此时,你能够改变指针的指向)
应该使用引用的情况: 如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,使用此时应使用引用。
引用&是一个变量的别名;例如: int& b=a;//b声明为a的引用,b和a拥有相同的内存地址,可以通过改变b的值来改变a包含的值。
常量指针表示存储在指针中的地址不能修改;像这样的指针只能指向初始化时指定的地址,但是,地址中的内容不是常量,可以修改;(和指向常量的指针不同,指向常量的指针表示指向的内容是常量,不能够修改)。
至于引用和常量指针的区别:int& b=a; int* const p=&a; b是a的引用,他们拥有相同的内存地址,指针p存储的是变量a的地址,指针p自己拥有另一个内存地址,可以通过间接运算符来解除指针的引用:int c=*p;
另外一个区别,引用不占用额外的内存,而指针是占内存的(存疑,https://blog.csdn.net/dead_of_winter/article/details/1641373)。
引用比较安全,指针用好了很好,用不好就非常不好。